mod example1 {
    pub struct Database {
        pub connection_string: String,
        pub timeout: u32,
        pub pool_size: u32,
    }

    pub fn print_database(database: &Database) {
        println!("Connection string: {}", database.connection_string);
        println!("Timeout: {}", database.timeout);
        println!("Pool size: {}", database.pool_size);
    }
}

mod example2 {
    // Database is now composed of three structs - ConnectionString, Timeout and
    // PoolSize.
    //
    // Let's decompose it into smaller structs.
    #[derive(Debug, Clone)]
    pub struct ConnectionString(pub String);

    #[derive(Debug, Clone, Copy)]
    pub struct Timeout(pub u32);

    #[derive(Debug, Clone, Copy)]
    pub struct PoolSize(pub u32);

    // We then compose these smaller structs back into `Database`.
    pub struct Database {
        pub connection_string: ConnectionString,
        pub timeout: Timeout,
        pub pool_size: PoolSize,
    }

    // print_database can then take ConnectionString, Timeout and Poolsize
    // structs instead.
    pub fn print_database(connection_str: ConnectionString, timeout: Timeout, pool_size: PoolSize) {
        println!("Connection string: {connection_str:?}");
        println!("Timeout: {timeout:?}");
        println!("Pool size: {pool_size:?}");
    }
}

fn main() {
    {
        let mut db = example1::Database {
            connection_string: "initial connection string".to_string(),
            timeout: 30,
            pool_size: 100,
        };
        example1::print_database(&db);

        #[allow(unused_variables)]
        let connection_string = &mut db.connection_string;

        // An immutable borrow of `db` happens here.
        example1::print_database(&db);
        // A mutable borrow is used here. Doesn't compile!
        // *connection_string = "new connection string".to_string();
    }

    println!();

    {
        // Initialize the Database with the three structs.
        let mut db = example2::Database {
            connection_string: example2::ConnectionString("initial connection string".to_string()),
            timeout: example2::Timeout(30),
            pool_size: example2::PoolSize(100),
        };

        let connection_string = &mut db.connection_string;
        example2::print_database(connection_string.clone(), db.timeout, db.pool_size);
        *connection_string = example2::ConnectionString("new connection string".to_string());
    }
}
